<template>
	<view v-if="enable" class="vi_popup" :class="'vi_popup_'+inPoint" :style="mainStyle" @touchmove.stop.prevent>
		<view v-if="setting.useMask&&maskState"
			class="vi_popup_mask"
			:class="{'vi_popup_act': showPopup}"
			:style="maskStyle"
			@tap="onMaskClick" />
		<view class="vi_popup_content" :class="{'vi_popup_invalid':!showPopup}" :style="contentStyle">
			<slot />
		</view>
	</view>
</template>

<script>

export default {
	name: "viPopup",
	props: {
		// 开启动画
		animation:{
			type:[Boolean,null],
			default:null
		},
		//弹出位置，top顶部弹出、bottom底部弹出、center中部弹出、left左侧弹出、right右侧弹出
		point:{
			type:[String,null],
			default:null
		},
		useMask:{
			type:[Boolean,null],
			default:null
		},
		//点击遮罩关闭
		maskClick:{
			type:[Boolean,null],
			default:null
		},
		//圆角
		radius:{
			type:[String,Number,Array,null],
			default:""
		},
		bgColor:{
			type:[String,null],
			default:null
		},
		maskBgColor:{
			type:[String,null],
			default:""
		},
		//适配顶部安全区域（留出默认头部导航条距离），true：一直适配。false：一直关闭。数字：窗口宽高均小于数字时适配，否则关闭
		safeAreaTop:{
			type:[Boolean,Number,null],
			default:null
		},
		//适配底部安全区域（留出默认底部tab导航距离）
		safeAreaBottom:{
			type:[Boolean,null],
			default:null
		},
		zIndex:{
			type:[Number,String,null],
			default:null
		},
		//内容板块默认宽，自定义单位
		//""表示不设置，point为left、right时默认auto；top、bottom时默认100%
		width:{
			type:[String,null],
			default:""
		},
		//内容板块默认高，自定义单位
		//""表示不设置，point为left、right时默认100%；top、bottom时默认auto
		height:{
			type:[String,null],
			default:""
		},
		//手动偏移，限制popup的显示区，独立于safeArea设置，用于自定义导航及自定义tab栏等
		//可带单位，默认单位px。间隙部分会观测到动画断层，还需配合zIndex让外层内容遮住间隙部分
		//状态栏的占位计算应在外部执行
		shift:{
			type:[Object,null],
			default: ()=>({
				// left:0,
				// right:0,
				// top:0,
				// bottom:0
			})
		},
	},
	data() {
		return {
			enable: false,
			showPopup: false,
			inPoint:"",
			winInfo:{
				width: 0,
				height: 0,
				top: 0,
				bottom: 0
			},
			pointType: ["top", "center", "bottom", "left", "right","fade"],
			maskState: true,
			duration: 300,		//动画持续时长，须与css的transition变量一致
			openTimer:null,		//打开计时器，用来确定提交事件
			closeTimer:null,	//关闭计时器，用来确定提交事件
		}
	},
	computed: {
		//将默认值内含到css、计算属性等地方，只内含真值
		//规避特殊使用的情况下，由prop传入undefined、null等无效数据导致的，默认值失效问题。例如组件嵌套且需要组件间参数传递
		//HBuilder X3.3.13; 微信开发者工具stable 1.05.2111300
		//微信小程序会对传入的undefined、null值根据参数类型输出不同空值(false、""、null、0)，参数类型为null或未指定参数类型则输出null
		//H5会对传入的undefined取默认值，未设默认值则输出undefined。对传入的null值固定输出null
		//做参数传递时需警惕两者行为不同而造成的陷阱，主要问题出现在微信小程序的空值转化，输出非用户传入的空值、假值，使默认真值丢失
		//兼容空值取默认值的可行方案目前为，参数类型加入null如[Boolean,null]，判断传入参数是否为定义的类型，是则使用，否则赋予默认值
		setting(){
			return {
				useMask: this.$api.isBoolean(this.useMask)? this.useMask :true,
				animation: this.$api.isBoolean(this.animation)? this.animation :true,
				maskClick: this.$api.isBoolean(this.maskClick)? this.maskClick :true,
				bgColor: this.bgColor?(this.bgColor=="none"?"transparent":this.bgColor):"#fff",
				zIndex: this.$api.isValue(this.zIndex)? Number(this.zIndex)||0 :99,
			}
		},
		safeObj(){
			let res ={};
			let shift = this.shift ||{};
			if(["top","left","right"].includes(this.inPoint)){
				let safeAreaTop = this.$api.isValue(this.safeAreaTop)? this.safeAreaTop :500;
				if(safeAreaTop == true || (typeof(safeAreaTop)=="number" && this.winInfo.width<=safeAreaTop && this.winInfo.height <= safeAreaTop) ){
					res["top"] = this.winInfo.top + "px"
				}
			}
			if(this.inPoint == "bottom"){
				if(this.safeAreaBottom){
					res["bottom"] = this.winInfo.bottom + "px"
				}
			}
			//手动偏移，支持calc
			let direction = ["top","bottom", "left", "right"];
			for(let dir of direction){
				if(shift[dir]){
					let shiftVal = shift[dir],
						resVal = res[dir];
					shiftVal += this.$api.isNumber(shiftVal,true)? "px":"";
					if(resVal){
						if(shiftVal.startsWith("calc(")){
							shiftVal = this.$api.setCalc(shiftVal,resVal)
						}else{
							shiftVal = `calc(${[resVal,shiftVal].join(" + ")})`
						}
					}
					res[dir] = shiftVal
				}
			}
			return res
		},
		mainStyle(){
			let res ={
				"z-index": this.setting.zIndex
			};
			this.safeObj.top? res["top"]=this.safeObj.top :"";
			this.safeObj.bottom? res["bottom"]=this.safeObj.bottom :"";
			return this.$api.objToStr(res)
		},
		maskStyle(){
			let res = {
				"z-index": this.setting.zIndex -1,
			};
			this.maskBgColor? res["background-color"]=this.maskBgColor :"";
			!this.setting.animation? res["transition"]="0s" :"";
			return this.$api.objToStr(res)
		},
		contentStyle(){
			let res = {
				"background-color": this.setting.bgColor,
				"z-index": this.setting.zIndex,
			}
			!this.setting.animation? res["transition"]="0s" :"";
			//设置默认宽度
			if(this.width){
				if(["left","right"].includes(this.inPoint)&&this.width.endsWith("%")){
					res["width"] = this.$api.splitUnit(this.width)[0] +"vw"
				}else{
					res["width"] = this.width
				}
			}else if(["top","bottom"].includes(this.inPoint)){
				res["width"] = "100%"
			}
			//设置默认高度
			if(this.height){
				if(["top","bottom"].includes(this.inPoint)&&this.height.endsWith("%")){
					let reduceNum = 0;
					let reduceStr = "";
					//带calc字符需提取
					let safeTop = this.safeObj.top;
					if(safeTop){
						if(safeTop.includes("calc")){
							reduceStr += " - " + safeTop.match(/calc(.*)/)[1];
						}else if(!safeTop.includes("rpx") && safeTop.endsWith("px")){
							reduceNum += this.$api.splitUnit(safeTop)[0]
						}else{
							reduceStr += " - " + safeTop
						}
					}
					let safeBottom = this.safeObj.bottom;
					if(safeBottom){
						if(safeBottom.includes("calc")){
							reduceStr += " - " + safeBottom.match(/calc(.*)/)[1]
						}else if(!safeBottom.includes("rpx") && safeBottom.endsWith("px")){
							reduceNum += this.$api.splitUnit(safeBottom)[0]
						}else{
							reduceStr += " - " + safeBottom
						}
					}
					res["height"] = `calc(${this.$api.splitUnit(this.height)[0]}vh - ${reduceNum}px${reduceStr})`
				}else{
					res["height"] = this.height
				}
			}else if(["left","right"].includes(this.inPoint)){
				res["height"] = "100%"
			}
			//设置圆角
			let radius = this.radius;
			if(this.$api.isArray(radius)){
				let lt = radius[0] ||0,
					rt = radius[1] ||0,
					rb = radius[2] ||0,
					lb = radius[3] ||0;
				res["border-radius"] = `${lt} ${rt} ${rb} ${lb}`;
			}else if(radius){
				res["border-radius"] = radius
			}
			return this.$api.objToStr(res)
		},
	},
	methods: {
		open(options){
			//打开中或已打开，退出
			if((this.enable&&this.openTimer)||this.showPopup){
				this.$emit("opened");
				return
			}
			//如果有关闭计时器，清除
			if(this.closeTimer){
				clearTimeout(this.closeTimer);
				this.closeTimer =null;
			}
			let {point} = options||{};
			if(point && this.pointType.includes(point)){
				this.inPoint = point
			}
			this.enable = true;
			//延迟执行动画，否则不显示动画只闪烁，非H5给予较大值
			let delay = 50;
			// #ifndef H5
			delay = 100
			// #endif
			setTimeout(()=>{
				this.showPopup = true;
				this.openTimer = setTimeout(()=>{
					this.openTimer = null;
					this.$emit("opened")
				},this.duration)
			}, delay)
			//下一帧提交变更事件，确保内容已创建
			this.$nextTick(()=>{
				this.$emit("change",true)
			})
		},
		close(){
			//关闭中或已关闭，退出
			if((!this.showPopup&&this.closeTimer)||!this.enable){
				this.$emit("closed");
				return
			}
			//如果有打开计时器，清除
			if(this.openTimer){
				clearTimeout(this.openTimer);
				this.openTimer =null;
			}
			this.showPopup = false;
			//动画完成后卸载
			this.closeTimer = setTimeout(()=>{
				this.enable = false;
				this.closeTimer = null;
				this.$emit("closed");
			},this.duration)
			//提交变更事件
			this.$emit("change",false)
		},
		closeMask(){
			this.maskState = false
		},
		openMask(){
			this.maskState = true
		},
		onMaskClick(){
			this.$emit("maskClick")
			if(this.setting.maskClick){
				this.close()
			}
		},
		getScreenInfo(){
			try {
				const res = uni.getSystemInfoSync();
				Object.assign(this.winInfo, {
					width: res.windowWidth,
					height: res.windowHeight,
					top: res.windowTop ||0,
					bottom: res.windowBottom ||0
				})
			} catch (e) {
				console.log("获取系统信息失败",e);
			}
		}
	},
	watch:{
		"point":{
			immediate: true,
			handler(newVal){
				this.inPoint = this.pointType.includes(newVal)?newVal:"center";
			}
		}
	},
	mounted() {
		this.getScreenInfo();
	},
}
</script>
<style lang="scss">
$transition:.3s;
.vi_popup {
	position: fixed;
	top: 0;
	left: 0;
	right: 0;
	bottom: 0;
	overflow: hidden;
	display: flex;
	>.vi_popup_mask{
		opacity: 0;
		position: absolute;
		top: 0;
		left: 0;
		bottom: 0;
		right: 0;
		background-color: rgba(0,0,0,0.4);
		transition: $transition;
		&.vi_popup_act{
			opacity: 1;
		}
	}
	>.vi_popup_content{
		position: relative;
		transition: $transition;
	}
	&.vi_popup_left{
		justify-content: flex-start;
		align-items: center;
		>.vi_popup_content{
			height: 100%;
		}
		>.vi_popup_invalid{
			transform: translateX(-100%);
		}
	}
	&.vi_popup_right{
		justify-content: flex-end;
		align-items: center;
		>.vi_popup_content{
			height: 100%;
		}
		>.vi_popup_invalid{
			transform: translateX(100%);
		}
	}
	&.vi_popup_top{
		justify-content: center;
		align-items: flex-start;
		>.vi_popup_content{
			width: 100%;
		}
		>.vi_popup_invalid{
			transform: translateY(-100%);
		}
	}
	&.vi_popup_bottom{
		justify-content: center;
		align-items: flex-end;
		>.vi_popup_content{
			width: 100%;
		}
		>.vi_popup_invalid{
			transform: translateY(100%);
		}
	}
	&.vi_popup_center{
		justify-content: center;
		align-items: center;
		>.vi_popup_invalid{
			opacity: 0;
			transform: scale(1.2);
		}
	}
	&.vi_popup_fade{
		justify-content: center;
		align-items: center;
		>.vi_popup_invalid{
			opacity: 0;
		}
	}
}
</style>
